package cn.sc.summer.token.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.MatchUtil;
import cn.sc.summer.redis.util.RouteUtil;
import cn.sc.summer.token.encrypt.RSAUtil;
import cn.sc.summer.token.encrypt.properties.EncryptProperties;
import cn.sc.summer.token.enums.RequestTypeEnum;
import cn.sc.summer.token.service.EncryptService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.*;

/**
 * 类名：rsa加解密实现类
 *
 * @author a-xin
 * @date 2024/5/11 15:17
 */
@Slf4j
@Service
public class EncryptServiceImpl implements EncryptService {

    @Resource
    private EncryptProperties encryptProperties;

    /**
     * 生成3组RSA秘钥对
     *
     * @param args 系统参数信息
     */
    public static void main(String[] args) throws NoSuchAlgorithmException {

        Map<String, String> serverKey = RSAUtil.getRSAKeyString(1024);
        System.err.println("server秘钥：" + serverKey);

        Map<String, String> webKey = RSAUtil.getRSAKeyString(1024);
        System.out.println("web秘钥：" + webKey);

        Map<String, String> feignKey = RSAUtil.getRSAKeyString(1024);
        System.err.println("feign秘钥：" + feignKey);

    }

    /**
     * 校验此次请求路径是都需要加密数据
     *
     * @param request 请求体
     * @return 是否需要加密：true：需要加密，false：不需要加密
     */
    @Override
    public Boolean checkEncrypt(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return checkEncrypt(requestURI);
    }

    /**
     * 校验此次请求路径是都需要加密数据
     *
     * @param uri 请求路径，例如：/getUrl，/getRoute
     * @return 是否需要加密：true：需要加密，false：不需要加密
     */
    @Override
    public Boolean checkEncrypt(String uri) {
        String rawPath = RouteUtil.getRawPath(ProjectConstant.SERVER_NAME, uri);
        List<String> allEncryptWhite = getAllEncryptWhite();
        if (CollUtil.isEmpty(allEncryptWhite)) {
            return Boolean.FALSE;
        }
        if (CharSequenceUtil.isNotBlank(rawPath) && MatchUtil.anyMatchValue(rawPath, allEncryptWhite)) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 校验此次请求路径是都需要解密数据
     *
     * @param request 请求体
     * @return 是否需要加密：true：需要解密，false：不需要解密
     */
    @Override
    public Boolean checkDecrypt(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return checkDecrypt(requestURI);
    }

    /**
     * 校验此次请求路径是都需要解密数据
     *
     * @param uri 请求路径，例如：/getUrl，/getRoute
     * @return 是否需要加密：true：需要解密，false：不需要解密
     */
    @Override
    public Boolean checkDecrypt(String uri) {
        String rawPath = RouteUtil.getRawPath(ProjectConstant.SERVER_NAME, uri);
        List<String> allDecryptWhite = getAllDecryptWhite();
        if (CollUtil.isEmpty(allDecryptWhite)) {
            return Boolean.FALSE;
        }
        if (CharSequenceUtil.isNotBlank(rawPath) && MatchUtil.anyMatchValue(rawPath, allDecryptWhite)) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 返回数据选择性加密（根据配置文件与请求类型决策是否进行数据加密）
     *
     * @param request   请求体
     * @param sourceStr 原始数据
     * @return 加密后数据
     */
    @Override
    public String encrypt(HttpServletRequest request, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        String requestType = request.getHeader(TokenConstant.REQUEST_TYPE);
        RequestTypeEnum requestTypeEnum = Optional.ofNullable(RequestTypeEnum.getByType(requestType))
                .orElse(RequestTypeEnum.GATEWAY_NORMAL);
        return encrypt(requestTypeEnum, sourceStr);
    }

    /**
     * 返回数据选择性加密（根据配置文件与请求类型决策是否进行数据加密）
     *
     * @param requestType 请求类型
     * @param sourceStr   原始数据
     * @return 加密后数据
     */
    @Override
    public String encrypt(String requestType, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        RequestTypeEnum requestTypeEnum = Optional.ofNullable(RequestTypeEnum.getByType(requestType))
                .orElse(RequestTypeEnum.GATEWAY_NORMAL);
        return encrypt(requestTypeEnum, sourceStr);
    }

    /**
     * 返回数据选择性加密（根据配置文件与请求类型决策是否进行数据加密）
     *
     * @param requestTypeEnum 请求类型
     * @param sourceStr       原始数据
     * @return 加密后数据
     */
    @Override
    public String encrypt(RequestTypeEnum requestTypeEnum, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        if (requestTypeEnum.equals(RequestTypeEnum.GATEWAY_NORMAL)) {
            return encryptData(sourceStr);
        } else if (requestTypeEnum.equals(RequestTypeEnum.OPEN_FEIGN)) {
            return encryptFeignData(sourceStr);
        } else {
            return sourceStr;
        }
    }

    /**
     * 入参数据选择性解密（根据配置文件与请求类型决策是否进行数据解密）
     *
     * @param request   请求体
     * @param sourceStr 原始数据
     * @return 加密后数据
     */
    @Override
    public String decrypt(HttpServletRequest request, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        String requestType = request.getHeader(TokenConstant.REQUEST_TYPE);
        RequestTypeEnum requestTypeEnum = Optional.ofNullable(RequestTypeEnum.getByType(requestType))
                .orElseThrow(() -> new RuntimeException("The request is made in an incorrect manner!"));
        return decrypt(requestTypeEnum, sourceStr);
    }

    /**
     * 入参数据选择性解密（根据配置文件与请求类型决策是否进行数据解密）
     *
     * @param requestType 请求类型
     * @param sourceStr   原始数据
     * @return 加密后数据
     */
    @Override
    public String decrypt(String requestType, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        RequestTypeEnum requestTypeEnum = Optional.ofNullable(RequestTypeEnum.getByType(requestType))
                .orElseThrow(() -> new RuntimeException("The request is made in an incorrect manner!"));
        return decrypt(requestTypeEnum, sourceStr);
    }

    /**
     * 入参数据选择性解密（根据配置文件与请求类型决策是否进行数据解密）
     *
     * @param requestTypeEnum 请求类型
     * @param sourceStr       原始数据
     * @return 加密后数据
     */
    @Override
    public String decrypt(RequestTypeEnum requestTypeEnum, String sourceStr) {
        if (CharSequenceUtil.isBlank(sourceStr)) {
            return "";
        }
        if (requestTypeEnum.equals(RequestTypeEnum.GATEWAY_NORMAL)) {
            return decryptData(sourceStr);
        } else if (requestTypeEnum.equals(RequestTypeEnum.OPEN_FEIGN)) {
            return decryptFeignData(sourceStr);
        } else {
            return sourceStr;
        }
    }

    /**
     * 加密openFeign调用参数
     *
     * @param data 需要加密信息
     * @return 加密后数据
     */
    @Override
    public String encryptFeignData(@NonNull String data) {
        try {
            if (!encryptProperties.getEncryptFeign()) {
                return data;
            }
            if (CharSequenceUtil.isBlank(data)) {
                return data;
            }
            data = StringEscapeUtils.unescapeJava(data);
            String feignPublicKey = encryptProperties.getFeignPublicKey();
            if (CharSequenceUtil.isBlank(feignPublicKey)) {
                throw new RuntimeException("The parameter is incorrect!");
            }
            RSAPublicKey publicKey = RSAUtil.getPublicKey(feignPublicKey);
            String encryptStr = RSAUtil.encryptByPublicKey(data, publicKey);
            if (encryptProperties.getEncryptLog()) {
                log.info("==> The encrypt type: {}, encrypt string: {}", RequestTypeEnum.OPEN_FEIGN.getType(), data);
                log.info("==> The encrypt result: {}", encryptStr);
            }
            return StringEscapeUtils.unescapeJava(encryptStr).replaceAll("\"", "");
        } catch (Exception e) {
            throw new RuntimeException("The parameter is incorrect!");
        }
    }

    /**
     * 解密openFeign返回数据
     *
     * @param data 需要解密数据
     * @return 解密后数据
     */
    @Override
    public String decryptFeignData(@NonNull String data) {
        try {
            if (!encryptProperties.getEncryptFeign()) {
               return data;
            }
            if (CharSequenceUtil.isBlank(data)) {
                return data;
            }
            String feignPrivateKey = encryptProperties.getFeignPrivateKey();
            if (CharSequenceUtil.isBlank(feignPrivateKey)) {
                throw new RuntimeException("The parameter is incorrect!");
            }
            RSAPrivateKey privateKey = RSAUtil.getPrivateKey(feignPrivateKey);
            data = StringEscapeUtils.unescapeJava(data).replaceAll("\"", "");
            String decryptStr = RSAUtil.decryptByPrivateKey(data, privateKey);
            if (encryptProperties.getEncryptLog()) {
                log.info("==> The decrypt type: {}, decrypt string: {}", RequestTypeEnum.OPEN_FEIGN.getType(), data);
                log.info("==> The decrypt result: {}", decryptStr);
            }
            return decryptStr;
        } catch (Exception e) {
            log.error("==> The feign result: {}", data);
            throw new RuntimeException("The parameter is incorrect!");
        }
    }

    /**
     * 加密数据
     *
     * @param data 需要加密数据
     * @return 加密后数据
     */
    @Override
    public String encryptData(@NonNull String data) {
        try {
            if (CharSequenceUtil.isBlank(data) || !encryptProperties.getEncrypt() || !encryptProperties.getEncryptResp()) {
                return data;
            }
            String serverPublicKey = encryptProperties.getServerPublicKey();
            if (CharSequenceUtil.isBlank(serverPublicKey)) {
                throw new RuntimeException("The parameter is incorrect!");
            }
            RSAPublicKey publicKey = RSAUtil.getPublicKey(serverPublicKey);
            String encryptStr = RSAUtil.encryptByPublicKey(data, publicKey);
            if (encryptProperties.getEncryptLog()) {
                log.info("==> The encrypt type: {}, encrypt string: {}", RequestTypeEnum.GATEWAY_NORMAL.getType(), data);
                log.info("==> The encrypt result: {}", encryptStr);
            }
            return StringEscapeUtils.unescapeJava(encryptStr).replaceAll("\"", "");
        } catch (Exception e) {
            throw new RuntimeException("The parameter is incorrect!");
        }
    }

    /**
     * 解密数据
     *
     * @param data 需要解密数据
     * @return 解密后数据
     */
    @Override
    public String decryptData(@NonNull String data) {
        try {
            if (CharSequenceUtil.isBlank(data) || !encryptProperties.getEncrypt() || !encryptProperties.getDecryptReq()) {
                return data;
            }
            String webPrivateKey = encryptProperties.getWebPrivateKey();
            if (CharSequenceUtil.isBlank(webPrivateKey)) {
                throw new RuntimeException("The parameter is incorrect!");
            }
            RSAPrivateKey privateKey = RSAUtil.getPrivateKey(webPrivateKey);
            String trimData = StringEscapeUtils.unescapeJava(data).replaceAll("\"", "");
            String decryptStr = RSAUtil.decryptByPrivateKey(trimData, privateKey);
            if (encryptProperties.getEncryptLog()) {
                log.info("==> The decrypt type: {}, decrypt string: {}", RequestTypeEnum.GATEWAY_NORMAL.getType(), trimData);
                log.info("==> The decrypt result: {}", decryptStr);
            }
            return decryptStr;
        } catch (Exception e) {
            throw new RuntimeException("The parameter is incorrect!");
        }
    }


    /**
     * 获取所有解密白名单
     *
     * @return 解密白名单
     */
    @Override
    public List<String> getAllDecryptWhite() {
        String[] decryptWhite = encryptProperties.getDecryptWhite();
        String[] serverDecryptWhite = encryptProperties.getServerDecryptWhite();

        List<String> serverDecryptWhiteList = Arrays.asList(serverDecryptWhite);
        List<String> decryptWhiteList = Arrays.asList(decryptWhite);
        List<String> resultList = new ArrayList<>();
        resultList.addAll(decryptWhiteList);
        resultList.addAll(serverDecryptWhiteList);
        return resultList;
    }

    /**
     * 获取所有加密白名单
     *
     * @return 所有加密白名单
     */
    @Override
    public List<String> getAllEncryptWhite() {
        String[] encryptWhite = encryptProperties.getEncryptWhite();
        String[] serverEncryptWhite = encryptProperties.getServerEncryptWhite();

        List<String> serverEncryptWhiteList = Arrays.asList(serverEncryptWhite);
        List<String> encryptWhiteList = Arrays.asList(encryptWhite);
        List<String> resultList = new ArrayList<>();
        resultList.addAll(serverEncryptWhiteList);
        resultList.addAll(encryptWhiteList);
        return resultList;
    }

}
